http2: fix stream WINDOW_UPDATE over-crediting buffered body bytes (FLOW_CONTROL_ERROR with slow readers) - #24
Open
rgarcia wants to merge 1 commit into
Conversation
transportResponseBody.Read computed the stream receive-window refresh as
unsent = streamFlow - available + bufPipe.Len()
bufPipe.Len() is body data received but not yet consumed by the
application. The amount that is safe to return to the peer is
streamFlow - available - buffered (what golang.org/x/net/http2 computes),
so the buffered term must be subtracted. Adding it over-credits the
stream window by 2x buffered on every refresh.
With a slow reader (proxy relaying to a backpressured client, rate-
limited download), the receive buffer stays large, the advertised stream
window desyncs from the real connection accounting, and a large download
dies partway with:
stream error: stream ID N; FLOW_CONTROL_ERROR
Add a regression test that downloads 48 MiB over an in-process server
through a Transport configured with a Chrome-like 6 MiB stream window
while consuming the body at ~12 MiB/s. It fails on master with
FLOW_CONTROL_ERROR after ~46 MiB and passes with this fix.
Refs bogdanfinn/tls-client#257
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the fhttp half of bogdanfinn/tls-client#257.
Problem
When a response body is consumed slower than the origin delivers it (a proxy relaying to a backpressured client, a rate-limited download), a large HTTP/2 download aborts partway with:
Root cause
In
transportResponseBody.Read, the stream receive-window refresh was:bufPipe.Len()is body data received but not yet consumed by the application. The credit that is safe to return to the peer isstreamFlow - available - buffered(whatgolang.org/x/net/http2computes), so the buffered term must be subtracted. Adding it over-credits the stream window by2 × bufferedon every refresh; with a slow reader the buffer stays large, the advertised window desyncs from the real accounting, and the stream dies with FLOW_CONTROL_ERROR once the connection-level window is exhausted.Fix
One-line sign fix:
Regression test
TestTransportSlowReaderLargeResponsedownloads 48 MiB from an in-process h2 server through aTransportconfigured with a Chrome-like 6 MiB initial stream window / 15.6 MiB connection flow, consuming the body at ~12 MiB/s.stream error: stream ID 1; FLOW_CONTROL_ERRORTest plan
go test -vet=off ./http2 -run TestTransportSlowReaderLargeResponse— fails before, passes aftergo test -vet=off -short ./http2— no new failures; theTestTransporttimeout andTestTransportH2cfailure reproduce identically on unmodified master (live-network dependent) and are unrelated to this change